import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.TitledBorder;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Random;

public class BubbleSortPanel extends JPanel {
	
	private ArrayList<LabeledRectangle> theArray;

	private JPanel CenterPanel, WorkPanel,HelpPanel;	
	
	private JButton sortButton,
					randomButton,
					stopButton,
					stepButton;
	
	private JLabel ErrorLabel, 
				   pseudoLabel_Do, 
				   pseudoLabel_SwapFalse, 
				   pseudoLabel_For,
				   pseudoLabel_If,
				   pseudoLabel_DoSwap,
				   pseudoLabel_SwapTrue,
				   pseudoLabel_EndIf,
				   pseudoLabel_EndFor,
				   pseudoLabel_While;
	
	private final int MAX_ARRAY_SIZE = 10;
	private final int ARRAY_START_Y = 35;
	private final int ARRAY_START_X = 10;
	private final int NODE_WIDTH = 75;
	private final int NODE_HEIGHT = 75;
	private final int NODE_SPACER = 10;
	
	private int STEP_THROUGH_I = 0; // if the user is stepping through the algorithm, keep track of their position
	private int STEP_THROUGH_LOOPS = 0; // count how many times the algorithm has looped through the array
	
	private boolean STEP_THROUGH_SWAPPED = false;
	private boolean AUTO_STEP_STOP = false;
	
	private final Color NODE_COLOR = new Color(0x00746b);
	private final Font LabelFont = new Font("Dialog", 1, 14);
	
	// draw the bubble sort panel consisting of a control, workspace, and help panel. 
	public BubbleSortPanel()
	{
		theArray = new ArrayList<LabeledRectangle>();
		
		WorkPanel = new JPanel();
		HelpPanel = new JPanel();
		CenterPanel = new JPanel();
		
		JPanel PseudoPanel = PseudoPanel();
		PseudoPanel.setBounds(ARRAY_START_X, 110, 450, 275);
		
		this.setBackground(Color.white);
		WorkPanel.setBackground(Color.white);
		WorkPanel.setLayout(null);
		WorkPanel.add(PseudoPanel);
		
		HelpPanel.setPreferredSize(new Dimension(0, 75));
		HelpPanel.setLayout(new BoxLayout(HelpPanel, BoxLayout.Y_AXIS));
		
		JLabel HelpLabel = new JLabel("Bubble Sort is a simple sorting algorithm with a worst-case and average complexity of O(n^2).");
		
		HelpLabel.setFont(LabelFont);
		HelpLabel.setForeground(Color.black);
		HelpLabel.setAlignmentX(CENTER_ALIGNMENT);
		
		JLabel HelpLabel1 = new JLabel("With a few tweaks the algorithm can have increased efficiency. Consider some options while stepping through.");	
		HelpLabel1.setFont(LabelFont);
		HelpLabel1.setForeground(Color.black);
		HelpLabel1.setAlignmentX(CENTER_ALIGNMENT);
		
		ErrorLabel = new JLabel();	
		ErrorLabel.setFont(LabelFont);
		ErrorLabel.setForeground(Color.red);
		ErrorLabel.setAlignmentX(CENTER_ALIGNMENT);
		
		HelpPanel.add(HelpLabel);
		HelpPanel.add(HelpLabel1);
		HelpPanel.add(Box.createRigidArea(new Dimension(0,10)));
		HelpPanel.add(ErrorLabel);
		
		createArray(MAX_ARRAY_SIZE);
		
		BorderLayout blayout2 = new BorderLayout();
		blayout2.setHgap(3);
		blayout2.setVgap(3);
		
		CenterPanel.setLayout(blayout2);
		CenterPanel.add(WorkPanel, BorderLayout.CENTER);
		CenterPanel.add(HelpPanel, BorderLayout.SOUTH);
		
		BorderLayout blayout = new BorderLayout();
		blayout.setHgap(3);
		blayout.setVgap(3);
		
		this.setLayout(blayout);
		this.add(CenterPanel, BorderLayout.CENTER);
		this.add(ControlPanel(), BorderLayout.EAST);
		
	}
	
	public JPanel PseudoPanel()
	{
		JPanel thisPanel = new JPanel();	
		
		TitledBorder tb = BorderFactory.createTitledBorder("Pseudocode");
		tb.setBorder(BorderFactory.createLineBorder(Color.lightGray));
		tb.setTitleFont(LabelFont);
		
		thisPanel.setBackground(Color.white);
		thisPanel.setBorder(tb);
		thisPanel.setFont(LabelFont);
		thisPanel.setLayout(new BoxLayout(thisPanel, BoxLayout.Y_AXIS));
		
		pseudoLabel_Do = new JLabel();
		pseudoLabel_SwapFalse = new JLabel();
		pseudoLabel_For = new JLabel();
		pseudoLabel_If = new JLabel();
		pseudoLabel_DoSwap = new JLabel();
		pseudoLabel_SwapTrue = new JLabel();
		pseudoLabel_EndIf = new JLabel();
		pseudoLabel_EndFor = new JLabel();
		pseudoLabel_While = new JLabel();

		pseudoLabel_Do.setFont(LabelFont);
		pseudoLabel_SwapFalse.setFont(LabelFont);
		pseudoLabel_For.setFont(LabelFont);
		pseudoLabel_If.setFont(LabelFont);
		pseudoLabel_DoSwap.setFont(LabelFont);
		pseudoLabel_SwapTrue.setFont(LabelFont);
		pseudoLabel_EndIf.setFont(LabelFont);
		pseudoLabel_EndFor.setFont(LabelFont);
		pseudoLabel_While.setFont(LabelFont);
		
		pseudoLabel_Do.setText("do");
		pseudoLabel_SwapFalse.setText("     swapped = false");
		pseudoLabel_For.setText("     for each item in the array minus one");
		pseudoLabel_If.setText("         if the current value is greater than the next value");
		pseudoLabel_DoSwap.setText("             swap(current, next)");
		pseudoLabel_SwapTrue.setText("             swapped = true");
		pseudoLabel_EndIf.setText("         end if");
		pseudoLabel_EndFor.setText("     end for");
		pseudoLabel_While.setText("while swapped = true");
		
		thisPanel.add(pseudoLabel_Do);
		thisPanel.add(Box.createRigidArea(new Dimension(0,10)));
		thisPanel.add(pseudoLabel_SwapFalse);
		thisPanel.add(Box.createRigidArea(new Dimension(0,10)));
		thisPanel.add(pseudoLabel_For);
		thisPanel.add(Box.createRigidArea(new Dimension(0,10)));
		thisPanel.add(pseudoLabel_If);
		thisPanel.add(Box.createRigidArea(new Dimension(0,10)));
		thisPanel.add(pseudoLabel_DoSwap);
		thisPanel.add(pseudoLabel_SwapTrue);
		thisPanel.add(Box.createRigidArea(new Dimension(0,10)));
		thisPanel.add(pseudoLabel_EndIf);
		thisPanel.add(Box.createRigidArea(new Dimension(0,10)));
		thisPanel.add(pseudoLabel_EndFor);
		thisPanel.add(Box.createRigidArea(new Dimension(0,10)));
		thisPanel.add(pseudoLabel_While);
		
		return thisPanel;
	}
	
	public JPanel ControlPanel() {
		
		JPanel thisPanel = new JPanel();
		thisPanel.setPreferredSize(new Dimension(200, 0));
		thisPanel.setLayout(new BoxLayout(thisPanel, BoxLayout.Y_AXIS));
		
		JLabel bubbleSortLabel = new JLabel("Bubble Sort Operations");
		
		bubbleSortLabel.setFont(LabelFont);
		
		randomButton = new JButton("Randomize");
		stepButton = new JButton("Step Through");
		sortButton = new JButton("Auto-Sort");
		stopButton = new JButton("Stop Sorting");

		// add action listeners to the buttons
		ActionEventHandler aeh = new ActionEventHandler();
		randomButton.addActionListener(aeh);
		stepButton.addActionListener(aeh);
		sortButton.addActionListener(aeh);
		stopButton.addActionListener(aeh);
		
		bubbleSortLabel.setAlignmentX(CENTER_ALIGNMENT);
		randomButton.setAlignmentX(CENTER_ALIGNMENT);
		stepButton.setAlignmentX(CENTER_ALIGNMENT);
		sortButton.setAlignmentX(CENTER_ALIGNMENT);
		stopButton.setAlignmentX(CENTER_ALIGNMENT);
		
		thisPanel.add(Box.createRigidArea(new Dimension(0,10)));
		thisPanel.add(bubbleSortLabel);
		thisPanel.add(Box.createRigidArea(new Dimension(0,10)));
		thisPanel.add(randomButton);
		thisPanel.add(stepButton);
		thisPanel.add(sortButton);
		thisPanel.add(stopButton);
		
		stopButton.setEnabled(false); // only enable stop button when auto-stepping

		return thisPanel;
	}
	
	public void createArray(int size)
	{
		int startX = ARRAY_START_X;
		
		// populate the array with random items so that they can be sorted
		for (int i = 0; i < size; i++)
		{				
			theArray.add(new LabeledRectangle(startX, ARRAY_START_Y, NODE_WIDTH, NODE_HEIGHT, NODE_COLOR, "" + new Random().nextInt(100) + ""));
			startX += NODE_WIDTH + NODE_SPACER;	
		}	
	}
	
	public void enableButtons(boolean enabled)
	{		
		randomButton.setEnabled(enabled);
		stepButton.setEnabled(enabled);
		sortButton.setEnabled(enabled);
	}

	// this function simulates one step of the bubble sort algorithm
	// it also contains checks to ensure that it won't execute more than necessary
	public void bubbleSortStep(boolean autoStep)
	{	
		
		int speed_factor = 2; // slow down the animation if not auto-stepping
		
		if (autoStep)
			speed_factor = 1; //
			
		//the color and position of the previously checked pair of nodes must be restored.
		// this structure ensures that this will happen even when the algorithm loops again
		if (STEP_THROUGH_I > 0 || (STEP_THROUGH_I <= 0 && STEP_THROUGH_LOOPS > 0))
		{
			
			pseudoLabel_EndIf.setForeground(Color.black);
			pseudoLabel_EndFor.setForeground(Color.black);
			
			int index;
			
			if (STEP_THROUGH_I > 0)
				index = STEP_THROUGH_I;				
			else
			{
				index = theArray.size() - 1;
				STEP_THROUGH_LOOPS = 0;
				
				pseudoLabel_While.setForeground(Color.red);
				
				try {
					Thread.sleep(500 * speed_factor);
				} catch (InterruptedException e1) {
					e1.printStackTrace();
				}
				
				pseudoLabel_While.setForeground(Color.black);
				
			}
			
			theArray.get(index).setColor(NODE_COLOR);
			theArray.get(index - 1).setColor(NODE_COLOR);
			theArray.get(index).y += 10;
			theArray.get(index - 1).y += 10;
			
					
			repaint();
			
		}
		
		if (STEP_THROUGH_I == -1)
		{
			ErrorLabel.setText("The array is sorted. Select 'Randomize' to obtain new values");
		}
		
		// if the algorithm execution is still necessary...
		if (STEP_THROUGH_I < theArray.size() - 1 && STEP_THROUGH_I  >= 0)
		{	
			
			// only highlight the "swapped = false" at the beginning of each loop
			if (STEP_THROUGH_I == 0)
			{
				pseudoLabel_SwapFalse.setForeground(Color.red);
				
				try {
					Thread.sleep(500 * speed_factor);
				} catch (InterruptedException e1) {
					e1.printStackTrace();
				}
				
				pseudoLabel_SwapFalse.setForeground(Color.black);
			}
			
			pseudoLabel_For.setForeground(Color.red);
			
			try {
				Thread.sleep(500 * speed_factor);
			} catch (InterruptedException e1) {
				e1.printStackTrace();
			}
			
			pseudoLabel_For.setForeground(Color.black);	
			
			
			// grab the next two nodes to be compared
			LabeledRectangle itemOne = theArray.get(STEP_THROUGH_I);
			LabeledRectangle itemTwo = theArray.get(STEP_THROUGH_I + 1);
			
			itemOne.setColor(Color.red);
			itemTwo.setColor(Color.red);
			
			itemOne.y -= 10;
			itemTwo.y -= 10;
								
			repaint();
			
			pseudoLabel_If.setForeground(Color.red);
			
			try {
				Thread.sleep(500 * speed_factor);
			} catch (InterruptedException e1) {
				e1.printStackTrace();
			}
			
			pseudoLabel_If.setForeground(Color.black);
			
			// compare and swap as needed
			if (Integer.parseInt(itemOne.getText()) > Integer.parseInt(itemTwo.getText()))
			{
							
				try {
					Thread.sleep(400 * speed_factor);
				} catch (InterruptedException e1) {
					e1.printStackTrace();
				}
				
				pseudoLabel_DoSwap.setForeground(Color.red);
				pseudoLabel_SwapTrue.setForeground(Color.red);
		
				String temp = itemOne.getText();
				itemOne.setText(itemTwo.getText());
				itemTwo.setText(temp);
				STEP_THROUGH_SWAPPED = true;
				
				repaint();	
				
				try {
					Thread.sleep(600 * speed_factor);
				} catch (InterruptedException e1) {
					e1.printStackTrace();
				}
				
				pseudoLabel_DoSwap.setForeground(Color.black);
				pseudoLabel_SwapTrue.setForeground(Color.black);
			
				
			}
			
			// if user auto steps through the sort, make sure there is a wait time
			// even if there is no swapping so they can see the comparison
			pseudoLabel_EndIf.setForeground(Color.red);
			
			if (autoStep)
			{
				try {
					Thread.sleep(500 * speed_factor);
				} catch (InterruptedException e1) {
					e1.printStackTrace();
				}
		
			}
			
			STEP_THROUGH_I++;
			
			if (STEP_THROUGH_I == theArray.size() - 1)
			{	
				
				try {
					Thread.sleep(500 * speed_factor);
				} catch (InterruptedException e1) {
					e1.printStackTrace();
				}
				
				pseudoLabel_EndIf.setForeground(Color.black);
				
				try {
					Thread.sleep(500 * speed_factor);
				} catch (InterruptedException e1) {
					e1.printStackTrace();
				}
				
				pseudoLabel_EndFor.setForeground(Color.red);
				
				try {
					Thread.sleep(500 * speed_factor);
				} catch (InterruptedException e1) {
					e1.printStackTrace();
				}
				
				STEP_THROUGH_LOOPS++;
				
				if (STEP_THROUGH_SWAPPED) // we need to loop through again to confirm things are in order
				{
					STEP_THROUGH_I = 0;
					STEP_THROUGH_SWAPPED = false;									
				}
				else
					STEP_THROUGH_I = -1; // the array is sorted,					
			}
		}				
	}
	
	public void paint(Graphics g) {
			
		super.paint(g);

		// draw the stack and queue nodes to the panel
		for (int i = 0; i < theArray.size(); i++)
			theArray.get(i).draw(g);

	}

	private class ActionEventHandler implements ActionListener {

		public void actionPerformed(ActionEvent e) {

			ErrorLabel.setText(""); // reset the error label when a button is pressed
			
			if(e.getSource() == sortButton)
			{
		
				new Thread(new Runnable() {  
				    public void run() {  
				    	
				    	enableButtons(false);
				    	stopButton.setEnabled(true);
				    	
						while (STEP_THROUGH_I != -1 && !AUTO_STEP_STOP)
							bubbleSortStep(true);
						
						if (!AUTO_STEP_STOP)
							bubbleSortStep(true); // step one more time to restore position of last two nodes
						else
							AUTO_STEP_STOP = false;
						
						
						enableButtons(true);
				    	stopButton.setEnabled(false);
						
				    }
				}).start();	
				
			}
			else if (e.getSource() == randomButton)
			{
				STEP_THROUGH_I = 0; // reset the step through iterator
				STEP_THROUGH_LOOPS = 0;
				theArray.clear();
				createArray(MAX_ARRAY_SIZE);
				
				pseudoLabel_Do.setForeground(Color.black);
				pseudoLabel_SwapFalse.setForeground(Color.black);
				pseudoLabel_For.setForeground(Color.black);
				pseudoLabel_If.setForeground(Color.black);
				pseudoLabel_DoSwap.setForeground(Color.black);
				pseudoLabel_SwapTrue.setForeground(Color.black);
				pseudoLabel_EndIf.setForeground(Color.black);
				pseudoLabel_EndFor.setForeground(Color.black);
				pseudoLabel_While.setForeground(Color.black);
				
				repaint();
				
			}
			else if (e.getSource() == stepButton)
			{			
				
				new Thread(new Runnable() {  
				    public void run() { 
				    	enableButtons(false);
				    	bubbleSortStep(false);
				    	enableButtons(true);
				    }
				}).start();
				
				
			}
			else if (e.getSource() == stopButton)
			{
				AUTO_STEP_STOP = true;
			}
			
		}
		
	}

}


